package com.dycong.common.mybatis.plugin;

import com.alibaba.fastjson.JSON;
import com.dycong.common.mybatis.annotation.ColumnLengthControl;
import com.dycong.common.mybatis.handler.ColumnLengthControlHandler;
import com.dycong.common.mybatis.util.ColumnLengthControlUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.springframework.beans.BeanUtils;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Properties;


/**
 * 说明因为表情符导致字段加密后比较长，所以对其进行裁剪,获取数据库column的长度，然后进行裁剪，该类的主要目的就是在设置参数时裁剪, mybatis plugin
 *
 * @see 代理而不是覆写,其实通过拦截器可以做CURL日志，仅仅针对表情过滤的话可以通过 TypeHandler 就够了啊（可能是因为在那里获取不到数据库表字段元数据吧）
 * author: xiaoqiang_zhu
 * date: 2019-07-01
 */
@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})})
@Slf4j
public class ParameterInterceptor implements Interceptor {

    private static final String PARAMETER_OBJECT = "parameterObject";
    private static final String BOUND_SQL = "boundSql";
    private static final String MAPPED_STATEMENT = "mappedStatement";
    private static final String INTO_INDEX = "into";
    private static final  String UPDATE_INDEX = "update";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invokeUpdate(invocation);
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    private Object invokeUpdate(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        try {
            if (log.isDebugEnabled()) {
                log.debug("invokeUpdate begin args:{}", invocation.getArgs());
            }
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
            // 反射获取 参数对象
            Field parameterField = parameterHandler.getClass().getDeclaredField(PARAMETER_OBJECT);
            parameterField.setAccessible(Boolean.TRUE);
            Object parameterObject = parameterField.get(parameterHandler);

            //检查是否指定的注解
            if (checkAnnotation(parameterObject)) {
                return invocation.proceed();
            }

            // 反射获取 BoundSql 对象，此对象包含生成的sql和sql的参数map映射
            Field boundSqlField = parameterHandler.getClass().getDeclaredField(BOUND_SQL);
            boundSqlField.setAccessible(Boolean.TRUE);
            BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);

            // 获取第一个参数
            PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];
            //获取mappedStatement

            Field mappedStatementField = parameterHandler.getClass().getDeclaredField(MAPPED_STATEMENT);
            mappedStatementField.setAccessible(Boolean.TRUE);
            MappedStatement mappedStatement = (MappedStatement) mappedStatementField.get(parameterHandler);
            //只有insert 和update 才进行处理
            if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT || mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
                List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

                //获取表名字
                String tableName = getTableName(boundSql, mappedStatement);

                //只有TypeHandler有EmojiHandler才会进行处理，其他的跳过,如果获取的表名字是空的也会跳过处理
                if (CollectionUtils.isEmpty(parameterMappings) || checkEmojiHandler(parameterMappings) || StringUtils.isEmpty(tableName)) {
                    log.info("invokeUpdate skip args:{}", invocation.getArgs());
                    return invocation.proceed();
                }
                //todo 获取该表和对应column的长度， 通过 Connection 获取 MetaData，每获取一次 MetaData，会从 information_schema 库表查询异常
                ResultSet resultSet = ps.getConnection().getMetaData().getColumns(null, "", tableName, null);

                HashMap<String, Integer> map = new HashMap<>();
                //字段和字段长度建立映射关系
                buildColumnValueMap(map, resultSet);
                //参数替换
                processParam(parameterObject, parameterMappings, map);

                // 改写的参数设置到原parameterHandler对象
                parameterField.set(parameterHandler, parameterObject);
                parameterHandler.setParameters(ps);
                if (log.isDebugEnabled()) {
                    log.debug("invokeUpdate end args:{}", invocation.getArgs());
                }
            }
        } catch (Exception e) {
            log.error("invokeUpdate error method:{},args:{}", invocation.getMethod(), invocation.getArgs(), e);
            return invocation.proceed();
        }
        return invocation.proceed();
    }

    /**
     * 获取要处理表的名字
     *
     * @param boundSql
     * @param mappedStatement
     * @return
     */
    private String getTableName(BoundSql boundSql, MappedStatement mappedStatement) {
        String temp;
        try {
            temp = boundSql.getSql().toLowerCase();
            if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
                String table = StringUtils.substring(temp, temp.indexOf(INTO_INDEX) + INTO_INDEX.length(), temp.indexOf('('));
                log.info(" getTableName 截取的tableName insert table:{},boundSql:{}", table, JSON.toJSONString(boundSql));
                return table.trim();
            } else if (mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
                String table = StringUtils.substring(temp, temp.indexOf(UPDATE_INDEX) + UPDATE_INDEX.length(), temp.indexOf("set"));
                log.info(" getTableName 截取的tableName update table:{},boundSql:{}", table, JSON.toJSONString(boundSql));
                return table.trim();
            }
        } catch (Exception e) {
            log.error(" getTableName 截取的tableName getTableName boundSql:{}", JSON.toJSONString(boundSql));
            return null;
        }
        return null;
    }


    /**
     * 检查是否包含EmojiStrHandler 在参数TypeHandler里面
     *
     * @param parameterMappings
     * @return
     */
    private boolean checkEmojiHandler(List<ParameterMapping> parameterMappings) {
        for (ParameterMapping mapping : parameterMappings) {
            //如果存在表情符Emoji 则进行处理，其他的不进行处理
            if (mapping.getTypeHandler() instanceof ColumnLengthControlHandler) {
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }


    /**
     * 通过反射 对参数的数值调用EmojiMaxLimitUtils.emojiMaxLimitHandle 方法
     * @param parameterObject
     * @param list
     * @param map
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws SQLException
     */
    private void processParam(Object parameterObject, List<ParameterMapping> list, HashMap<String, Integer> map) throws IllegalAccessException, InvocationTargetException, SQLException {
        // 如果参数是bean，反射设置值
        for (ParameterMapping mapping : list) {
            if (mapping.getTypeHandler() instanceof ColumnLengthControlHandler) {
                //对参数的数值调用EmojiMaxLimitUtils.emojiMaxLimitHandle 方法
                PropertyDescriptor ps = BeanUtils.getPropertyDescriptor(parameterObject.getClass(), mapping.getProperty());
                if (ps != null && ps.getReadMethod() != null && ps.getWriteMethod() != null) {
                    String value = String.valueOf(ps.getReadMethod().invoke(parameterObject));
                    if (StringUtils.isNotEmpty(value)) {
                        Integer maxLength = map.get(mapping.getProperty().toLowerCase());
                        if (maxLength == null || maxLength == 0) {
                            log.info("ParamInterceptor processParam parameterObject: {} map: {} ", JSON.toJSONString(parameterObject), JSON.toJSONString(map));
                            throw new RuntimeException("从数据库获取colomn长度的值异常");
                        }
                        ps.getWriteMethod().invoke(parameterObject, ColumnLengthControlUtil.emojiMaxLimitHandle(value, maxLength));
                        log.info("ParamInterceptor success parameterObject: {}", JSON.toJSONString(parameterObject));
                    }
                }
            }
        }
    }

    private void buildColumnValueMap(HashMap<String, Integer> map, ResultSet resultSet) throws SQLException {
        while (resultSet.next()) {
            map.put(resultSet.getString("COLUMN_NAME").replaceAll("_", "").toLowerCase(), Integer.parseInt(resultSet.getString("COLUMN_SIZE")));
        }
    }

    /**
     * 检查是否是指定的Annotation
     *
     * @param parameterObject
     * @return
     */
    private boolean checkAnnotation(Object parameterObject) {

        if (Objects.isNull(parameterObject)) {
            return Boolean.TRUE;
        }


        Annotation beans[] = parameterObject.getClass().getAnnotations();
        if (ArrayUtils.isNotEmpty(beans)) {
            for (Annotation annotation : beans) {
                if (annotation.annotationType().isAssignableFrom(ColumnLengthControl.class)) {
                    return Boolean.FALSE;
                }
            }
        }
        return Boolean.TRUE;
    }
}
